package com.server.shop;

import com.google.gson.Gson;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoNamespace;
import com.mongodb.client.*;
import com.mongodb.client.model.Filters;
import com.mongodb.client.result.DeleteResult;
import com.server.shop.data.BaseData;
import com.server.shop.data.Good;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;

@SpringBootApplication
@RestController
@CrossOrigin(origins = "*")
public class ShopApplication {

	private static final String connection_string = "mongodb://localhost:27017";
	private static final String database_name = "shop";
	private static final String database_table_user = "user";
	private static final String database_table_user_name = "name";
	private static final String database_table_user_password = "password";
	private static final String database_table_user_like = "like";

	private static final String database_table_user_token = "token";


	private static final String database_table_good = "good";
	private static final String database_table_good_comments = "comments";
	private static final String database_table_good_price = "price";

	private static MongoDatabase mongoDatabase;


	private final static float w= 1.1222509145736694f;
	private final static float b = -0.13216687738895416f;

	private final static float scale_x = 0.005f;
	private final static float scale_y = 0.0002f;

	public static void main(String[] args) {
		SpringApplication.run(ShopApplication.class, args);
		try{
			MongoClientSettings settings = MongoClientSettings.builder()
					.applyConnectionString(new ConnectionString(connection_string))
					.build();
			MongoClient mongoClient = MongoClients.create(settings);

			try {
				MongoNamespace.checkDatabaseNameValidity(database_name);
			} catch (IllegalArgumentException e) {
			}
			// 连接到数据库
			mongoDatabase = mongoClient.getDatabase(database_name);
			System.out.println("Connect to database successfully");
			insertAdmin();
		}catch(Exception e){
			System.err.println( e.getClass().getName() + ": " + e.getMessage() );
		}
	}

	private static void insertAdmin() {
		MongoCollection<Document> userTable = null;
		try{
			userTable = mongoDatabase.getCollection(database_table_user);
		} catch (Exception ignored){}
		if (userTable == null) {
			mongoDatabase.createCollection(database_table_user);
			userTable = mongoDatabase.getCollection(database_table_user);
		}
		Document admin = userTable.find(Filters.eq(database_table_user_name, "admin")).first();
		if (admin != null) return;
		admin = new Document();
		admin.put(database_table_user_name, "admin");
		admin.put(database_table_user_password, "admin");
		userTable.insertOne(admin);
		System.out.println("insert admin account.");
	}

	private String generateToken(String user) {
		return Base64.getEncoder().encodeToString((user + System.currentTimeMillis()).getBytes());
	}

	private boolean checkUser(String name, String token) {
		if (name == null || token == null || name.isEmpty() || token.isEmpty()) {
			return false;
		}
		MongoCollection<Document> userTable = mongoDatabase.getCollection(database_table_user);
		Document find = userTable.find(Filters.eq(database_table_user_name, name)).first();
		if (find == null) {
			return false;
		}
		return token.equals(find.get(database_table_user_token));
	}

	@PostMapping("/login")
	@ResponseBody
	public BaseData login(@RequestParam(value = database_table_user_name) String name, @RequestParam(value = database_table_user_password) String password) {
		boolean exist = false;
		for (String listCollectionName : mongoDatabase.listCollectionNames()) {
			if (listCollectionName.equals(database_table_user)) {
				exist = true;
				break;
			}
		}
		if (!exist) {
			mongoDatabase.createCollection(database_table_user);
		}
		BaseData ret = new BaseData();
		if (name == null || name.isEmpty() || password == null || password.isEmpty()) {
			return ret;
		}
		MongoCollection<Document> userTable = mongoDatabase.getCollection(database_table_user);
		Document find = userTable.find(Filters.eq(database_table_user_name, name)).first();

		if (find != null && String.valueOf(find.get(database_table_user_password)).equals(password)) {
			ret.setSuccess(true);
			ret.setMsg("login success! user: " + name);
			Map<String, String> map = new HashMap<>(2);
			map.put(database_table_user_token, generateToken(name));
			userTable.updateOne(Filters.eq(database_table_user_name, name), new Document("$set",new Document(database_table_user_token,map.get(database_table_user_token))));
			ret.setData(map);
		} else {
			ret.setSuccess(false);
			ret.setMsg("login failed!");
		}
		System.out.println(ret.getMsg());
		return ret;
	}

	@PostMapping("/removeUser")
	@ResponseBody
	public BaseData removeUser(@RequestParam(value = "userName") String userName) {
		BaseData ret = new BaseData();
		MongoCollection<Document> userTable = mongoDatabase.getCollection(database_table_user);
		DeleteResult deleteResult = userTable.deleteOne(Filters.eq(database_table_user_name, userName));
		long deletedCount = deleteResult.getDeletedCount();
		if (deletedCount == 0) {
			ret.setMsg("not found user.");
		} else {
			ret.setSuccess(true);
		}
		return ret;
	}

	@PostMapping("/removeGood")
	@ResponseBody
	public BaseData removeGood(@RequestParam(value = "goodName") String goodName) {
		BaseData ret = new BaseData();
		MongoCollection<Document> goodTable = mongoDatabase.getCollection(database_table_good);
		DeleteResult deleteResult = goodTable.deleteOne(Filters.eq("title", goodName));
		long deletedCount = deleteResult.getDeletedCount();
		if (deletedCount == 0) {
			ret.setMsg("not found user.");
		} else {
			ret.setSuccess(true);
		}
		return ret;
	}

	@PostMapping("/removeComment")
	@ResponseBody
	public BaseData removeComment(@RequestParam(name = "commentToken") String commentToken, @RequestParam(name = "goodId")String goodId) {
		BaseData ret = new BaseData();
		MongoCollection<Document> goodTable = mongoDatabase.getCollection(database_table_good);

		Document good = goodTable.find(Filters.eq("_id", new ObjectId(goodId))).first();
		if (good == null) {
			ret.setMsg("good id error.");
			return ret;
		}
		try{
			List list = new Gson().fromJson(good.get(database_table_good_comments).toString(), List.class);
			for (Object o : list) {
				Map<String, String> map = (Map<String, String>) o;
				if (map.get("token").equals(commentToken)) {
					list.remove(o);
					break;
				}
			}
			goodTable.updateOne(Filters.eq("_id", new ObjectId(goodId)), new Document("$set", new Document(database_table_good_comments, new Gson().toJson(list))));
			ret.setSuccess(true);
		}catch (Exception e){
			ret.setMsg(e.toString());
		}
		return ret;
	}

	@PostMapping("/register")
	@ResponseBody
	public BaseData register(@RequestParam(value = database_table_user_name) String name, @RequestParam(value = database_table_user_password) String password) {
		boolean exist = false;
		for (String listCollectionName : mongoDatabase.listCollectionNames()) {
			if (listCollectionName.equals(database_table_user)) {
				exist = true;
				break;
			}
		}
		if (!exist) {
			mongoDatabase.createCollection(database_table_user);
		}
		MongoCollection<Document> userTable = mongoDatabase.getCollection(database_table_user);
		Document find = userTable.find(Filters.eq(database_table_user_name, name)).first();

		BaseData ret = new BaseData();
		if (find != null && find.get(database_table_user_password, String.class).equals(password)) {
			ret.setSuccess(false);
			ret.setMsg("register failed! user existed: " + name);
		} else {
			Document document = new Document();
			document.put(database_table_user_name, name);
			document.put(database_table_user_password, password);
			userTable.insertOne(document);
			ret.setSuccess(true);
			ret.setMsg("register success!");
		}
		System.out.println(ret.getMsg());
		return ret;
	}

	@PostMapping("/manageData")
	@ResponseBody
	public BaseData manageData() {
		BaseData ret = new BaseData();
		Map<String, Object> retMap = new HashMap<>();
		MongoCollection<Document> userTable = mongoDatabase.getCollection(database_table_user);
		List<Map<String, Object>> users = new ArrayList<>();
		for (Document document : userTable.find()) {
			if ("admin".equals(document.get(database_table_user_name))) {
				continue;
			}
			users.add(document);
		}
		retMap.put("users", users);
		MongoCollection<Document> goodTable = mongoDatabase.getCollection(database_table_good);
		List<Map<String, Object>> goods = new ArrayList<>();
		List<Object> comments = new ArrayList<>();

		for (Document document : goodTable.find()) {
			goods.add(document);
			Object cs = document.get(database_table_good_comments);
			if (cs == null) continue;
			comments.addAll(new Gson().fromJson(cs.toString(), List.class));
		}
		retMap.put("goods", goods);
		retMap.put("comments", comments);
		ret.setData(retMap);
		ret.setSuccess(true);
		return ret;
	}

	@PostMapping("/addComment")
	@ResponseBody
	public BaseData addComment(@RequestParam(name = "token") String token, @RequestParam(name = "goodId") String goodId, @RequestParam(name = "comment") String comment) {
		BaseData ret = new BaseData();
		Document user = mongoDatabase.getCollection(database_table_user).find(Filters.eq(database_table_user_token, token)).first();
		if (user == null){
			ret.setMsg("token error.");
			return ret;
		}
		String userId = user.get("_id").toString();
		String userName = user.get(database_table_user_name).toString();
		MongoCollection<Document> goodTable = mongoDatabase.getCollection(database_table_good);
		Document good = goodTable.find(Filters.eq("_id", new ObjectId(goodId))).first();
		if (good == null) {
			ret.setMsg("good invalid.");
			return ret;
		}
		Calendar calendar = Calendar.getInstance();
		int year = calendar.get(Calendar.YEAR);
		int month = calendar.get(Calendar.MONTH) + 1;
		int day = calendar.get(Calendar.DAY_OF_MONTH);
		String commentToken = generateToken(userId);
		Object comments = good.get(database_table_good_comments);
		Map<String, String> map = new HashMap<>();
		map.put("token", commentToken);
		map.put("goodId", goodId);
		map.put("userId", userId);
		map.put("userName", userName);
		map.put("comment", comment);
		map.put("goodTitle", good.get("title").toString());
		map.put("date", year+"-"+month+"-"+day);
		List<Object> list;
		if (comments == null) {
			list = new ArrayList<>();
		} else {
			list = new Gson().fromJson(comments.toString(), List.class);
		}
		list.add(map);
		goodTable.updateOne(Filters.eq("_id", new ObjectId(goodId)), new Document("$set", new Document(database_table_good_comments, new Gson().toJson(list))));
		ret.setSuccess(true);
		return ret;
	}

	@PostMapping("/getLike")
	@ResponseBody
	public BaseData getLike(@RequestParam(name = database_table_user_token) String token) {
		MongoCollection<Document> userTable = mongoDatabase.getCollection(database_table_user);
		Document first = userTable.find(Filters.eq(database_table_user_token, token)).first();
		BaseData ret = new BaseData();
		if (first == null) {
			ret.setMsg("token error.");
			return ret;
		}
		List<Good> list = new ArrayList<>();
		Object likes = first.get(database_table_user_like);
		if (likes != null) {
			MongoCollection<Document> goodTable = mongoDatabase.getCollection(database_table_good);
			String[] ids = likes.toString().split(",");
			for (String id : ids) {
				Document document = goodTable.find(Filters.eq("_id", new ObjectId(id))).first();
				if (document != null) {
					Good good =new Good();
					good.price = Float.parseFloat(String.valueOf(document.get("price")));
					good.title = document.get("title", String.class);
					good.pic = document.get("pic", String.class);
					good.type = document.get("type", Integer.class);
					good.id = String.valueOf(document.get("_id"));
					list.add(good);
				}
			}
		}
		ret.setSuccess(true);
		ret.setData(list);
		return ret;
	}

	@PostMapping("/addLike")
	@ResponseBody
	public BaseData addLike(@RequestParam(name = database_table_user_token) String token, @RequestParam(name = "goodId") String goodId) {
		MongoCollection<Document> userTable = mongoDatabase.getCollection(database_table_user);
		Document first = userTable.find(Filters.eq(database_table_user_token, token)).first();
		BaseData ret = new BaseData();
		if (first == null) {
			ret.setMsg("token error.");
			return ret;
		}
		Object likes = first.get(database_table_user_like);
		if (likes == null) likes = goodId;
		else {
			if (likes.toString().contains(goodId)) {
				ret.setMsg("已经加入购物车");
				return ret;
			}
			likes = String.valueOf(likes) + "," + goodId;
		}
		userTable.updateOne(Filters.eq(database_table_user_token, token), new Document("$set", new Document(database_table_user_like, likes)));
		ret.setSuccess(true);
		return ret;
	}

	@PostMapping("/requestData")
	@ResponseBody
	public BaseData requestData(@RequestParam(value = "type") int type, @RequestParam(value = "area") String area, @RequestParam(value = "price_start") String ps, @RequestParam(value = "price_end") String pe) {
		insertGood();
		MongoCollection<Document> goodTable = mongoDatabase.getCollection(database_table_good);
		if (type < 1 || type > 4) type = 1;
		FindIterable<Document> documents = goodTable.find(Filters.eq("type", type));
		float price = -1;
		float pStart = -1, pEnd = -1;
		try {
			price = (float) predict(Float.parseFloat(area));
		} catch (Exception e) {}
		try {

			pStart = Float.parseFloat(ps);
			pEnd = Float.parseFloat(pe);
		} catch (Exception e) {}
		List<Good> list = new ArrayList<>();
		for (Document document : documents) {
			Good good = new Good();
			good.price = Float.parseFloat(String.valueOf(document.get("price")));
			if (pStart > 0 && pEnd > 0 && pStart < pEnd) {
				if (good.price < pStart || good.price > pEnd) {
					continue;
				}
			}
			good.title = document.get("title", String.class);
			good.pic = document.get("pic", String.class);
			good.type = document.get("type", Integer.class);
			try {
				good.details = document.get("details", List.class);
			} catch (Exception ignored) {
			}
			good.comments = document.get(database_table_good_comments) == null ? null : String.valueOf(document.get(database_table_good_comments));
			good.id = String.valueOf(document.get("_id"));
			if (price > 0)
				good.d = price - good.price;
			list.add(good);
		}
		if (price > 0) {
			float predictPrice = price;
			list.sort(new Comparator<Good>() {
				@Override
				public int compare(Good o1, Good o2) {
					int d1 = (int) (o1.price - predictPrice);
					int d2 = (int) (o2.price - predictPrice);
					return Math.abs(d1) - Math.abs(d2);
				}
			});
		}
		BaseData ret = new BaseData();
		ret.setSuccess(true);
		ret.setData(list);
		return ret;
	}

	@PostMapping("/addGood")
	@ResponseBody
	public BaseData addGood(@RequestParam(name = "goodTitle") String goodTitle,@RequestParam(name = "goodType") String goodType, @RequestParam(name = "goodPrice") String goodPrice, @RequestParam(name = "goodImage") String goodImage,
							@RequestParam(name = "goodDetails") String goodDetails) {
		BaseData ret = new BaseData();
		try {
			Document document = new Document();
			document.put(database_table_good_price, Float.parseFloat(goodPrice));
			document.put("title", goodTitle);
			document.put("pic", goodImage);
			document.put("type", Integer.parseInt(goodType));
			document.put("details", Arrays.stream(goodDetails.split(",")).toList());
			MongoCollection<Document> goodTable = mongoDatabase.getCollection(database_table_good);
			goodTable.insertOne(document);
			ret.setSuccess(true);
		} catch (Exception e) {
			System.out.println(e);
			ret.setMsg(e.getMessage());
		}
		return ret;
	}

	@PostMapping("/posts")
	@ResponseBody
	public BaseData posts(@RequestParam(name = "file") MultipartFile multipartFile) {
		String projectDir = System.getProperty("user.dir");
		BaseData ret = new BaseData();
		try {
			File file = new File(projectDir + "/pics");
			if (!file.exists()) {
				boolean mkdirs = file.mkdirs();
				if (!mkdirs) {
					ret.setMsg("mkdir failed.");
					return ret;
				}
			}
			String fileName = System.currentTimeMillis() + "_" + multipartFile.getOriginalFilename();
			InputStream inputStream = multipartFile.getInputStream();
			String filePath = file.getPath() + "/" + fileName;
			FileOutputStream fileOutputStream = new FileOutputStream(filePath);
			ret.setData("http://localhost:8080/pics/" + fileName);
			byte[] buffer = new byte[1024];
			int length;
			while ((length = inputStream.read(buffer)) > 0) {
				fileOutputStream.write(buffer, 0, length);
			}
			fileOutputStream.flush();
			fileOutputStream.close();
			inputStream.close();
		} catch (IOException e) {
			ret.setMsg(e.getMessage());
			return ret;
		}
		ret.setSuccess(true);
		return ret;
	}

	private double predict(float area) {
		float x = area * scale_x;
		x *= w;
		x += b;
		x /= scale_y;
		return x;
	}

	private void insertGood() {
		try {
			MongoCollection<Document> collection = mongoDatabase.getCollection(database_table_good);
			Document document = collection.find(Filters.eq("type", 1)).first();
			if (document != null) return;
			collection.drop();
		} catch (Exception ignored){}
		mongoDatabase.createCollection(database_table_good);
		MongoCollection<Document> goodTable = mongoDatabase.getCollection(database_table_good);
		String[] pics = {
				"https://img.youpin.mi-img.com/shopmain/7dcad12da60a7629670f862c224ef3c5.png@base@tag=imgScale&F=webp&h=800&w=800?w=800&h=800",
				"https://img.youpin.mi-img.com/shopmain/a71387f2542843244b0aecb97f94d9cf.png@base@tag=imgScale&F=webp&h=800&w=800?w=800&h=800",
				"https://img.youpin.mi-img.com/youpin_gms/d9d40cfd_6835_4f8c_83da_5bd701bdc463.png@base@tag=imgScale&F=webp&h=800&w=800?w=800&h=800",
				"https://img.youpin.mi-img.com/youpin_gms/4e09ec6f_3326_4077_bf88_07320e67799d.png@base@tag=imgScale&F=webp&h=800&w=800?w=800&h=800"
		};
		String[] name1 = {
				"米家冰箱 十字对开门430L",
				"米家冰箱尊享版550L",
				"米家冰箱 十字对开门520L",
				"米家风冷十字四门冰箱 496L",
				"云米451升草本除菌智能冰箱四开门",
				"云米518升臻彩触控屏智能冰箱四开门",
		};
		String[] name2 = {
				"小米电视 全面屏 EA75",
				"小米电视 EA65",
				"Redmi MAX 86英寸电视",
				"小米电视5 65英寸",
				"小米电视 EA43",
				"小米电视6 至尊版",
		};
		String[] name3 = {
				"小米空调1.5匹新1级",
				"米家空调 鎏金版 2匹",
				"米家新风空调 尊享版 1.5匹",
				"米家空调 巨省电 睡眠款1.5匹",
				"小米空调 1匹 新1级",
				"小米新1级立式空调",
		};
		String[] name4 = {
				"3公斤智能壁挂滚筒洗衣机",
				"智能壁挂滚筒洗衣机",
				"直驱滚筒洗衣机10kg",
				"2.5公斤智能除菌迷你滚筒洗衣机6TX",
				"滚筒洗衣机尊享版10kg",
				"2.5公斤智能滚筒除菌洗衣机",
		};
		for (int i = 400; i <= 20000; i += 50) {
			Document document = new Document();
			document.put("price", i + new Random().nextInt(20));
			document.put("type", 1);
			document.put("title", name1[new Random().nextInt(name1.length)]);
			document.put("pic", pics[0]);
			goodTable.insertOne(document);
			document = new Document();
			document.put("price", i + new Random().nextInt(20));
			document.put("type", 2);
			document.put("title", name2[new Random().nextInt(name2.length)]);
			document.put("pic", pics[1]);
			goodTable.insertOne(document);
			document = new Document();
			document.put("price", i + new Random().nextInt(20));
			document.put("type", 3);
			document.put("title", name3[new Random().nextInt(name3.length)]);
			document.put("pic", pics[2]);
			goodTable.insertOne(document);
			document = new Document();
			document.put("price", i + new Random().nextInt(20));
			document.put("type", 4);
			document.put("title", name4[new Random().nextInt(name4.length)]);
			document.put("pic", pics[3]);
			goodTable.insertOne(document);
		}
	}

}
